package com.stone.bypass.proxy.core.support;

import com.stone.bypass.proxy.core.anno.CreateDiversionProxy;
import com.stone.bypass.proxy.core.anno.Diverter;
import com.stone.bypass.proxy.core.config.DiverterAnnoConfig;
import com.stone.bypass.proxy.core.utils.StringUtils;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.asm.Type;
import org.springframework.beans.factory.BeanFactory;

/**
 * @Auther: stone
 * @Date: 2021/3/7 17:30
 * @Description:
 */
public class MethodProxyHandler<T> implements InvocationHandler {

    private static final Logger LOGGER = LoggerFactory.getLogger(MethodProxyHandler.class);
    /**
     * bean version-object
     */
    private Map<String, Object> proxyMap;
    private BeanFactory beanFactory;
    private Class<T> customInterface;
    private static final Map<String, DiverterAnnoConfig> divertedMethodMap = new ConcurrentHashMap<>();
    private static final Map<String, DiversionSelector> selectorMap = new ConcurrentHashMap<>();

    public MethodProxyHandler(Map<String, Object> proxyMap, Class<T> customInterface, BeanFactory beanFactory) {
        this.proxyMap = proxyMap;
        this.customInterface = customInterface;
        this.beanFactory = beanFactory;
        loadAllMehodConfigCache();
    }

    private void loadAllMehodConfigCache() {
        Method[] methods = customInterface.getMethods();
        for (int i = 0; i < methods.length; i++) {
            loadDivertedAnnoConfig(methods[i]);
        }
    }


    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        DiverterAnnoConfig diverterAnnoConfig = getDivertedAnnoConfig(method);
        if (StringUtils.isAllEmpty(diverterAnnoConfig.getKey())) {
            Object resultTarget = diverterAnnoConfig.getSelector().nonDivert(proxyMap);
            return method.invoke(resultTarget, args);
        }
        MethodInovkeContext mic = new MethodInovkeContext();
        mic.setMethod(method);
        mic.setArgs(args);
        String key = String.valueOf(diverterAnnoConfig.getKeyEvaluator().apply(mic));
        Object finalTarget = diverterAnnoConfig.getSelector().divert(key, proxyMap);
        return method.invoke(finalTarget, args);
    }

    public void loadDivertedAnnoConfig(Method method) {
        getDivertedAnnoConfig(method);
    }

    private DiverterAnnoConfig getDivertedAnnoConfig(Method method) {
        String key = getKey(method);
        DiverterAnnoConfig diverterAnnoConfig = divertedMethodMap.get(key);
        if (diverterAnnoConfig == null) {
            synchronized (divertedMethodMap) {
                diverterAnnoConfig = divertedMethodMap.get(key);
                if (diverterAnnoConfig == null) {
                    diverterAnnoConfig = parseDiverted(method);
                    divertedMethodMap.put(key, diverterAnnoConfig);
                }
            }
        }
        return diverterAnnoConfig;
    }

    private DiverterAnnoConfig parseDiverted(Method method) {
        Diverter diverter = method.getAnnotation(Diverter.class);
        if (diverter == null || StringUtils.isAllEmpty(diverter.key())) {
            DiverterAnnoConfig dac = new DiverterAnnoConfig();
            dac.setSelector(getSelector(null, method));
            return dac;
        }
        DiverterAnnoConfig dac = new DiverterAnnoConfig();
        dac.setKey(diverter.key());
        DiversionSelector selector = getSelector(diverter, method);
        dac.setSelector(selector);
        String[] keys = diverter.key();
        List<ExpressionEvaluator> evaluatorList = new ArrayList<>();
        for (int i = 0; i < keys.length; i++) {
            if (StringUtils.isNotEmpty(keys[i])) {
                evaluatorList.add(new ExpressionEvaluator(keys[i], method));
            }
        }
        dac.setKeyEvaluator((o) -> {
            for (ExpressionEvaluator evaluator : evaluatorList) {
                Object result = evaluator.apply(o);
                if (result != null) {
                    return result;
                }
            }
            return null;
        });
        return dac;
    }

    private DiversionSelector getSelector(Diverter diverter, Method method) {
        Class selector = DiversionSelector.NONE.class;
        if (diverter != null) {
            selector = diverter.selector();
        }
        if (selector == DiversionSelector.NONE.class) {
            Class clz = method.getDeclaringClass();
            CreateDiversionProxy createDiversionProxy = (CreateDiversionProxy) clz
                .getAnnotation(CreateDiversionProxy.class);
            selector = createDiversionProxy.selector();
        }
        String key = selector.getName();
        DiversionSelector diversionSelector = selectorMap.get(key);
        if (diversionSelector == null) {
            synchronized (selectorMap) {
                diversionSelector = selectorMap.get(key);
                if (diversionSelector == null) {
                    try {
                        diversionSelector = getBeanFromSpring(selector);
                        if (diversionSelector == null) {
                            diversionSelector = (DiversionSelector) selector.newInstance();
                        }
                        selectorMap.put(key, diversionSelector);
                    } catch (Exception e) {
                        throw new RuntimeException(selector.getName() + "初始化失败", e);
                    }

                }
            }
        }
        return diversionSelector;
    }

    private DiversionSelector getBeanFromSpring(Class selector) {
        try {
            return ((DiversionSelector) beanFactory.getBean(selector));
        } catch (Exception e) {
            // spring不存在
            return null;
        }
    }

    public static String getKey(Method method) {
        StringBuilder sb = new StringBuilder();
        sb.append(method.getDeclaringClass().getName());
        sb.append('.');
        sb.append(method.getName());
        sb.append(Type.getMethodDescriptor(method));
        return sb.toString();
    }

    public static String getMethodSimpleName(Method method) {
        StringBuilder sb = new StringBuilder();
        sb.append(method.getDeclaringClass().getSimpleName());
        sb.append('.');
        sb.append(method.getName());
        return sb.toString();
    }
}
